import os, re
from concurrent.futures import ThreadPoolExecutor
import subprocess
import ipaddress
import json
import requests
from app.core.setting import setting, Platform, ScanFlexMode
from app.core.exceptions import ScanGatewayException
from app.core.error_code import ErrorCode
import logging
from app.core.config.__logging__ import setup_logging
from typing import Optional, Any, List, Dict
from urllib3.exceptions import MaxRetryError

headers = {
    "Content-Type": "application/json",
    "Opentrons-Version": "3"
}

logger = logging.getLogger("discover.robot")


def get_default_gateway():
    """获取默认网关地址（兼容Windows/Linux/macOS）"""

    def extract_gateway_from_route_output(text):
        """
        从路由表输出中提取默认网关
        适用于 route print, netstat -r 等命令输出
        """
        patterns = [
            # 匹配默认路由行：0.0.0.0 0.0.0.0 网关IP 接口IP 跃点数
            r'0\.0\.0\.0\s+0\.0\.0\.0\s+(\d+\.\d+\.\d+\.\d+)\s+\d+\.\d+\.\d+\.\d+\s+\d+',
            # 匹配默认路由行：default via 网关IP
            r'default\s+via\s+(\d+\.\d+\.\d+\.\d+)',
            # 匹配包含 0.0.0.0 和网关IP的行
            r'0\.0\.0\.0.*?(\d+\.\d+\.\d+\.\d+).*?metric',
        ]

        for pattern in patterns:
            match = re.search(pattern, text)
            if match:
                return match.group(1)

        return None

    try:
        if setting.platform == Platform.WINDOWS:
            result = subprocess.run(['route', 'print', '0.0.0.0'],
                                    capture_output=True, text=True)
            lines = result.stdout.split('\n')
            for line in lines:
                gateway = extract_gateway_from_route_output(line)
                if gateway is not None:
                    return gateway
        elif setting.platform == Platform.LINUX:
            result = subprocess.run(
                ["ip", "route"],
                capture_output=True,
                text=True
            )
            for line in result.stdout.splitlines():
                if "default via" in line:
                    return line.split()[2]

        elif setting.platform == Platform.MAC:
            result = subprocess.run(
                ["netstat", "-rn"],
                capture_output=True,
                text=True
            )
            for line in result.stdout.splitlines():
                if "default" in line:
                    return line.split()[1]
        else:
            raise ScanGatewayException(error_code=ErrorCode.GET_GATEWAY_FAILED,
                                       detail=f"Get Wrong Platform {setting.platform}")

    except:
        raise ScanGatewayException(error_code=ErrorCode.GET_GATEWAY_FAILED,
                                   detail=f"Platform {setting.platform}\n"
                                          f"Can Not Get Gateway")


def check_ip(gateway, subnet_mask='24'):
    try:
        network = ipaddress.IPv4Network(f"{gateway}/{subnet_mask}", strict=False)
        logger.info(f"正在扫描网络: {network} (共 {network.num_addresses - 2} 个IP)")

        def ping_ip(ip):
            if setting.platform == Platform.WINDOWS:
                response = os.system(f"ping -n 1 -w 1000 {ip} > nul")  # Windows
            else:
                response = os.system(f"ping -c 1 -W 1 {ip} > /dev/null 2>&1")  # Linux/Mac
            if response == 0:
                return ip
            else:
                return None

        with ThreadPoolExecutor(max_workers=256) as executor:  # 调整max_workers控制并发数
            results = executor.map(ping_ip, [str(host) for host in network.hosts()])
            reachable_ips = [ip for ip in results if ip is not None]
        return reachable_ips
    except:
        raise ScanGatewayException(error_code=ErrorCode.SCAN_IP_FAILED)


def scan_ip() -> List[str]:
    try:
        default = setting.default_gateway
        if default:
            gateway = default
        else:
            gateway = get_default_gateway()
        if gateway is None:
            logger.error("Gateway is None, Please set a default gateway")
            raise ScanGatewayException(error_code=ErrorCode.GET_GATEWAY_FAILED, detail="No Default Gateway Found")
        ip_list = check_ip(gateway)
        logger.info(f"发现{len(ip_list)} 个在线IP")
        return ip_list
    except ScanGatewayException:
        logger.warning("Can Not Find IP Online")
        raise


def check_flex(ip) -> Optional[Dict[str, Optional[Dict[Any, Any]]]]:
    try:
        response = requests.get(f"http://{ip}:31950/health", headers=headers, timeout=setting.san_flex_timeout)
        if setting.scan_flex_mode == ScanFlexMode.STRICT:
            validation = response.status_code == 200
        elif setting.scan_flex_mode == ScanFlexMode.LOOSE:
            validation = response.status_code in {200, 502, 500}
        else:
            raise ScanGatewayException(error_code=ErrorCode.GET_GATEWAY_FAILED, detail="Wrong Scan Flex Code")
        if validation and response.status_code == 200:
            return {ip: json.loads(response.text)}
        elif validation and response.status_code in {502, 500}:
            return {ip: None}
        else:
            logger.info(f"Get wrong status_code {response.status_code}, Address: {ip}\n"
                        f"{response.text}")
            return None
    except (ConnectionRefusedError, MaxRetryError, requests.exceptions.ConnectionError):
        return None
    except Exception as error:
        raise ScanGatewayException(error_code=ErrorCode.GET_HEALTH_FAILED,
                                   detail=str(error))


def scan_flex():
    try:
        ip_list = scan_ip()
        with ThreadPoolExecutor(max_workers=100) as executor:  # 调整max_workers控制并发数
            results = executor.map(check_flex, ip_list)
            reachable_ips = {}
            [reachable_ips.update(ip) for ip in results if ip is not None]
        logger.info(f"发现{len(reachable_ips)}个在线Flex")
        return reachable_ips
    except ScanGatewayException:
        raise
    except Exception as error:
        raise ScanGatewayException(error_code=ErrorCode.UNKNOWN_ERROR, detail=str(error)) from e


def show_flex(_flex_group: Dict[Any, Any]):
    try:
        for key, value in _flex_group.items():
            if value is not None:
                logger.info(f'ip: {key},'
                            f' name: {value["name"]}, '
                            f'app_version: {value["api_version"]}, '
                            f'fw_version: {value["fw_version"]}')
            else:
                logger.info(f'ip: {key}, {value}')
    except:
        raise ScanGatewayException(error_code=ErrorCode.SCAN_IP_FAILED, detail="Show Flex Info Failed")


if __name__ == '__main__':
    setup_logging()
    try:
        flex_group = scan_flex()
        show_flex(flex_group)
    except ScanGatewayException as e:
        print(e)
